/*
 *  AVM.h
 *  OfflineTranslator
 *
 *  Created by PH on 5.9.10.
 *  Copyright 2010 Petr Homola. All rights reserved.
 *
 */

#ifndef NLP_AVM_H_
#define NLP_AVM_H_

#include "global.h"
#include "macros.h"
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <set>
#include <sstream>
#ifdef TR1
#include <tr1/unordered_map>
#include <tr1/functional>
#include <tr1/memory>
#else
#include <unordered_map>
#endif
#include <assert.h>
#include "Value.h"

namespace nlp {
	
	using namespace std;
#ifdef TR1
	using namespace std::tr1;
#endif
	
	//extern int avmAllocs;
	
	void unrollVector(const vector<vector<string> >* v, BLOCKVAR(block, void, const string&));
	
	class AVM {

		private:
		mutable unordered_map<string, Value> _map;
		shared_ptr<AVM> avm;
		shared_ptr<AVM> alternativeAvm;
		bool transferredStructurally;
		bool transferredLexically;
		bool synthesizedMorphologically;
		int penalty;
		//bool ownsAvm;
		//unsigned int retainCount;

		public:
		AVM() /*: retainCount(1)*/ {
			//avm = NULL;
			//alternativeAvm = NULL;
			transferredStructurally = false;
			transferredLexically = false;
			synthesizedMorphologically = false;
			penalty = 0;
			//cout << "########## 1 AVM alloced: " << ++avmAllocs << endl;
			//avmAllocs++;
		}
		
		AVM(const shared_ptr<AVM>& _avm) : avm(_avm) /*, ownsAvm(o), retainCount(1)*/ {
			alternativeAvm = _avm->alternativeAvm;
			//if (alternativeAvm != NULL) alternativeAvm->retain();
			//_avm->retain();
			transferredStructurally = _avm->isTransferredStructurally();
			transferredLexically = _avm->isTransferredLexically();
			synthesizedMorphologically = _avm->isSynthesizedMorphologically();
			penalty = _avm->penalty;
			//cout << "########## 2 AVM alloced: " << ++avmAllocs << endl;
			//avmAllocs++;
		}
		
		~AVM() {
			//if (alternativeAvm != NULL //) alternativeAvm->release();
			//	&& (avm == NULL || avm->alternativeAvm != alternativeAvm)) delete alternativeAvm;
			//if (avm != NULL) avm->release();
			//if (avm != NULL && ownsAvm) delete avm;
			//cout << "########## AVM dealloced: " << --avmAllocs << endl;
		}
		
		//void retain() { retainCount++; }
		
		//void release() { if (--retainCount == 0) delete this; }
		
		/*shared_ptr<AVM> addAlternativeAvm() {
			shared_ptr<AVM> me(this, true);
			shared_ptr<AVM> _avm(new AVM(me));
			_avm->alternativeAvm = this->alternativeAvm;
			this->alternativeAvm = _avm;
			//assert(this != getAlternativeAvm());
			return _avm;
		}*/
		
		shared_ptr<AVM> addAlternativeEmptyAvm() {
			assert(avm != NULL);
			//shared_ptr<AVM> me(this, true);
			//shared_ptr<AVM> _avm(new AVM(avm));
            auto _avm = MAKESHARED(AVM, avm);
			_avm->alternativeAvm = this->alternativeAvm;
			this->alternativeAvm = _avm;
			//assert(this != getAlternativeAvm());
			return _avm;
		}
		
		void addAlternativeAvm(const shared_ptr<AVM>& _avm) {
			_avm->alternativeAvm = this->alternativeAvm;
			this->alternativeAvm = _avm;
			//assert(this != getAlternativeAvm());
		}
		
		void setAlternativeAvm(const shared_ptr<AVM>& _avm) {
			//if (alternativeAvm != NULL) alternativeAvm->release();
			alternativeAvm = _avm;
			//if (_avm != NULL) _avm->retain();
		}
		
		shared_ptr<AVM> getAlternativeAvm() const { return alternativeAvm; }
		
		void increasePenalty(int delta) {
			penalty += delta;
		}
		
		int getPenalty() const { return penalty; }
		
		int getTreePenalty() const {
			__block int treePenalty = penalty;
			enumerateChildren(BLOCK(&treePenalty)(const string& key, const shared_ptr<AVM>& avm) {
				treePenalty += avm->getTreePenalty();
			}, false);
			return treePenalty;
		}
		
		void put(const string& key, const Value& value) {
			put(key, value, false);
		}
		
		void put(const string& key, const Value& value, bool putToAlternatives) {
			_map[key] = value;
			if (putToAlternatives && alternativeAvm != NULL) alternativeAvm->put(key, value, true);
		}
		
		const Value& get(const string& key) const {
			if (_map.find(key) == _map.end() && avm != NULL) return avm->get(key);
			return _map[key];
		}
		
		bool contains(const string& key) const {
			if (_map.find(key) != _map.end()) return true;
			if (avm != NULL) return avm->contains(key);
			return false;
		}
		
		void enumerateKeysAndValues(BLOCKVAR(block,void,const string&, const Value&)) const {
			set<string> used;
			enumerateKeysAndValues(used, block);
		}

		void enumerateKeysAndValues(set<string>& used, BLOCKVAR(block, void, const string&, const Value&)) const {
			for (unordered_map<string, Value>::const_iterator it = _map.begin(); it != _map.end(); ++it) {
				if (used.find(it->first) == used.end()) {
					block(it->first, it->second);
					used.insert(it->first);
				}
			}
			if (avm != NULL) avm->enumerateKeysAndValues(used, block);
		}
		
		void enumerateChildren(BLOCKVAR(block, void, const string&, const shared_ptr<AVM>&), bool recursively) const {
			enumerateLeftChildren(block, recursively);
			enumerateRightChildren(block, recursively);
		}

		void enumerateLeftChildrenSorted(BLOCKVAR(block, void, const string&, const shared_ptr<AVM>&), bool recursively) const {
			__block vector<string> keys;
			__block vector<shared_ptr<AVM> > avms;
			enumerateLeftChildren(BLOCK(&keys, &avms)(const string& key, const shared_ptr<AVM>& avm) {
				keys.push_back(key);
				avms.push_back(avm);
			}, recursively);
			float lastOrder = INT_MIN;
			while (true) {
				float min = INT_MAX;
				int index = -1;
				for (int i = 0; i < keys.size(); i++) {
					shared_ptr<AVM>& avm = avms[i];
					float order = atof(avm->get("order").description().c_str());
					if (order > lastOrder && order < min) {
						index = i;
						min = order;
					}
				}
				if (min == INT_MAX) break;
				lastOrder = min;
				block(keys[index], avms[index]);
			}
		}
		
		void enumerateLeftChildren(BLOCKVAR(block, void, const string&, const shared_ptr<AVM>&), bool recursively) const {
			Value _order = get("order");
			float order = atof(_order.description().c_str());
			__block unordered_map<float,string>* keys = new unordered_map<float,string>;
			__block unordered_map<float,shared_ptr<AVM> >* children = new unordered_map<float,shared_ptr<AVM> >;
			__block float order2;
			enumerateKeysAndValues(BLOCK(&keys, &children, &order2, order)(const string& key, const Value& value) {
				if (value.getAvm() != NULL) {
					order2 = atof(value.getAvm()->get("order").description().c_str());
					if (order2 < order) {
						//cout << "## " << key << " " << order2 << endl;
						(*children)[order2] = value.getAvm();
						(*keys)[order2] = key;
					}
				}
			});
			for (unordered_map<float,shared_ptr<AVM> >::const_iterator it = children->begin(); it != children->end(); ++it) {
				if (recursively) it->second->enumerateLeftChildren(block, true);
				block((*keys)[it->first], it->second);
				if (recursively) it->second->enumerateRightChildren(block, true);
			}
			delete children;
			delete keys;
		}

		void enumerateRightChildrenSorted(BLOCKVAR(block, void, const string&, const shared_ptr<AVM>&), bool recursively) const {
			__block vector<string> keys;
			__block vector<shared_ptr<AVM> > avms;
			enumerateRightChildren(BLOCK(&keys, &avms)(const string& key, const shared_ptr<AVM>& avm) {
				keys.push_back(key);
				avms.push_back(avm);
			}, recursively);
			float lastOrder = INT_MIN;
			while (true) {
				float min = INT_MAX;
				int index = -1;
				for (int i = 0; i < keys.size(); i++) {
					shared_ptr<AVM>& avm = avms[i];
					float order = atof(avm->get("order").description().c_str());
					if (order > lastOrder && order < min) {
						index = i;
						min = order;
					}
				}
				if (min == INT_MAX) break;
				lastOrder = min;
				block(keys[index], avms[index]);
			}
		}
		
		void enumerateRightChildren(BLOCKVAR(block, void, const string&, const shared_ptr<AVM>&), bool recursively) const {
			float order = atof(get("order").description().c_str());
			__block unordered_map<float,string>* keys = new unordered_map<float,string>;
			__block unordered_map<float,shared_ptr<AVM> >* children = new unordered_map<float,shared_ptr<AVM> >;
			__block float order2;
			enumerateKeysAndValues(BLOCK(&keys, &children, &order2, order)(const string& key, const Value& value) {
				if (value.getAvm() != NULL) {
					order2 = atof(value.getAvm()->get("order").description().c_str());
					if (order2 > order) {
						(*children)[order2] = value.getAvm();
						(*keys)[order2] = key;
					}
				}
			});
			for (unordered_map<float,shared_ptr<AVM> >::const_iterator it = children->begin(); it != children->end(); ++it) {
				if (recursively) it->second->enumerateLeftChildren(block, true);
				block((*keys)[it->first], it->second);
				if (recursively) it->second->enumerateRightChildren(block, true);
			}
			delete children;
			delete keys;
		}
		
		void linearize(BLOCKVAR(block, void, const string&), bool withAlternatives) const;
		
		string freeAttrMember(string attr) const {
			int n = 0;
			std::stringstream stream;
			stream << attr << "@" << ++n;
			string name = stream.str();
			while (contains(name)) {
				std::stringstream stream;
				stream << attr << "@" << ++n;
				name = stream.str();
			}
			return name;
		}
		
		string getForm() const {
			//if (desc.size() > 0) return desc;
			Value value = get("targetForm");
			if (value.isEmpty() == true) value = get("targetLemma");
			if (value.isEmpty() == true) {
				value = get("form");
				//cout << Value(_avm->get("form").description() + "/" + _avm->get("lemma").description()).description() << endl;
			}
			if (value.isEmpty()) return description(); else return value.description();
		}
		
		string description() const {
			__block string s = "(";
			enumerateKeysAndValues(BLOCK(&s)(const string& key, const Value& value) {
				s += key + ":" + value.description() + ",";
			});
			/*for (unordered_map<string, Value>::const_iterator it = _map.begin(); it != _map.end(); ++it) {
				Value value = it->second;
				s += it->first + ":" + value.description() + ",";
			}*/
			return s + ")";
		}
		
		bool equals(const AVM& _avm) const;
		
		bool isTransferredStructurally() const { return transferredStructurally; }
		
		void setTransferredStructurally() { transferredStructurally = true; }
		
		bool isTransferredLexically() const { return transferredLexically; }
		
		void setTransferredLexically() { transferredLexically = true; }
		
		bool isSynthesizedMorphologically() const { return synthesizedMorphologically; }
		
		void setSynthesizedMorphologically() { synthesizedMorphologically = true; }
		
	};

}

#endif
